package EcoSpeed;

import java.util.*;


/**
 * Class Lift
 */
public class Lift implements  Observer {

	/**
	 * Etat de l'ascenseur qui monte
	 */
    public static int MOVE_ASC = 1;
    /**
     * Etat de l'ascenseur qui descend
     */
    public static int MOVE_DESC = -1;
    /**
     * Etat de l'ascenseur qui ne bouge pas
     */
    public static int NO_MOVE = 0;
    /**
     * Si l'ascenseur est reserver pour une urgence
     */
    public boolean urgence = false;

    /**
     * Driver pour piloté l'ascenseur
     */
    private DriverLift driver;

    /**
     * Liste des séries d'étages à parcourir : Si state = -1 => Collections.reverseOrder() , Si state = 0 => null , si state = 1 => Collections
     */
    private LinkedList<ArrayList<Integer>> queueStoppingSeries;

    /**
     * Etat de l'ascenseur : -1 l'ascenseur descend , 0 l'ascenseur ne bouge pas , 1 l'ascenseur monte.
     */
    private int stateLift;

    /**
     * Constructeur
     * @param d driver de l'ascenseur
     * @throws NullPointerException si le driver n'existe pas
     */
    public Lift (DriverLift d) {
        if (d != null) {
            this.driver = d;

            queueStoppingSeries = new LinkedList<ArrayList<Integer>>();
            ArrayList<Integer> newStoppingSerie = new ArrayList<Integer>();

            queueStoppingSeries.add(newStoppingSerie);
            stateLift = NO_MOVE;

            d.addObserver(this);
        } else {
            throw new NullPointerException("L'interface de driverLift doit être définie.");
        }
    }

    /**
     * Get the value of queueStoppingSeries
     * @return the value of queueStoppingSeries
     */
    public LinkedList<ArrayList<Integer>> getQueueStoppingSeries() {
        return this.queueStoppingSeries;
    }

    /**
     * Modifie l'etat d'urgence de l'ascenseur
     * @param val nouvelle valeur
     */
    public void setUrgence(boolean val){
	    this.urgence = val;
    }

    /**
     * Ascesseur de l'état d'urgence
     * @return valeur de urgence
     */
    public boolean getUrgence(){
	    return this.urgence;
    }

    /**
     * Set the value of state
     * Etat de l'ascenseur : -1 l'ascenseur descend , 0 l'ascenseur ne bouge pas , 1
     * l'ascenseur monte.
     * @param newVar the new value of state
     */
    public void setState ( int newVar ) {
        this.stateLift = newVar;
    }

    /**
     * Get the value of state
     * Etat de l'ascenseur : -1 l'ascenseur descend , 0 l'ascenseur ne bouge pas , 1
     * l'ascenseur monte.
     * @return the value of state
     */
    public int getState(){
        return this.stateLift;
    }

    /**
     * Get the value of driver
     * Driver pour piloté l'ascenseur
     * @return the value of driver
     */
    public DriverLift getDriver() {
        return this.driver;
    }



    /**
     * Méthode ajoutant un nouvel étage à la liste des étages déservis par
     * l'ascenseur. Dans le cas où l'étage n'est pas dans le bon sens, une
     * nouvelle série d'étage est crée.
     * @return       boolean Réussite de l'ajout
     * @param        startFloor étage de début
     * @param        endFloor étage de fin
     */
    public boolean addStop( int startFloor, int endFloor) {

        // Paramètres non valides.
        if (startFloor < 0 || startFloor > Utils.NB_FLOORS || endFloor < 0 || endFloor > Utils.NB_FLOORS){
		return false;
	}
	
        int lastStep = this.getFloor();
        Iterator it = this.queueStoppingSeries.iterator();
        ArrayList tmp;
	boolean end = false;
	boolean firstList = true;
	int s = this.getRequestState(startFloor, endFloor);
        /* Pour tous les ArrayList */
	while(it.hasNext() && !end){
		tmp = (ArrayList)(it.next());
		if(!tmp.isEmpty()){
			int first = (Integer)tmp.get(0);
			int last = (Integer)tmp.get(tmp.size()-1);
			if(
				(
					s == this.getRequestState(lastStep, first) &&
					(
						(
							s == Lift.MOVE_ASC &&
							lastStep <= startFloor &&
							first >= endFloor
						) ||
						(
							s == Lift.MOVE_DESC &&
							lastStep >= startFloor &&
							first <= endFloor
						)
					)
				) ||
				(
					s == this.getRequestState(first, last) &&
					(
						(
							s == Lift.MOVE_ASC &&
							first <= startFloor &&
							last >= endFloor
						) ||
						(
							s == Lift.MOVE_DESC &&
							first >= startFloor &&
							last <= endFloor
						)
					)
				)
			){
				end = true;
				tmp.add(startFloor);
				tmp.add(endFloor);
				if (s == MOVE_ASC) {
					Collections.sort(tmp);
				} else {
					Collections.sort(tmp, Collections.reverseOrder());
				}
			}
			lastStep = last;
		}
	}
	if(!end){
		if(this.queueStoppingSeries.size() == 1 && this.queueStoppingSeries.get(0).isEmpty()){
			this.queueStoppingSeries.get(0).add(startFloor);
			this.queueStoppingSeries.get(0).add(endFloor);
			if (s == MOVE_ASC) {
				Collections.sort(this.queueStoppingSeries.get(0));
			} else {
				Collections.sort(this.queueStoppingSeries.get(0), Collections.reverseOrder());
			}
		}
		else{
			ArrayList<Integer> finalList = this.queueStoppingSeries.get(this.queueStoppingSeries.size()-1);
			int finalStat = this.getRequestState(finalList.get(0), finalList.get(finalList.size()-1));
			if(s == finalStat){
				finalList.add(startFloor);
				finalList.add(endFloor);
				if (s == MOVE_ASC) {
					Collections.sort(finalList);
				} else {
					Collections.sort(finalList, Collections.reverseOrder());
				}
			}
			else{
				ArrayList<Integer> newStoppingSerie = new ArrayList<Integer>();
				newStoppingSerie.add(startFloor);
				newStoppingSerie.add(endFloor);
				if (s == MOVE_ASC) {
					Collections.sort(newStoppingSerie);
				} else {
					Collections.sort(newStoppingSerie, Collections.reverseOrder());
				}
				this.queueStoppingSeries.add(newStoppingSerie);
			}
		}
	}
	this.driver.moveTo(this.queueStoppingSeries.get(0).get(0));
	this.stateLift = this.getRequestState(this.getFloor(), this.queueStoppingSeries.get(0).get(0));
        return true;
    }


    /**
     * Retourne le dernier étage que l'ascenseur va traversé, ou l'étage courrent si
     * l'asqcenseur ne bouge pas (state = 0)
     * @return       Integer
     */
    public Integer getFinalFloor() {
	    if(this.queueStoppingSeries.size() == 0 || this.queueStoppingSeries.get(this.queueStoppingSeries.size() - 1).isEmpty()){
		    return (Integer) this.getFloor();
	    }
	    else{
		    return (Integer) this.queueStoppingSeries.get(this.queueStoppingSeries.size()-1).get(this.queueStoppingSeries.get(this.queueStoppingSeries.size()-1).size()-1);
	    }
    }

    /**
     * Ascesseur de l'etage Actuel de l'ascneseur
     * @return       l'etage actuel
     */
    public int getFloor(  ) {
        return driver == null ? -1 : driver.getFloor();
    }


    /**
     * Ascesseur de la charge
     * @return       la charge
     */
    public int getWeight(  ) {
        int a;
        a= this.driver.getWeight();
        return (a);
    }

    /**
     * Vide la pile des trajet a faire par un ascenseur
     */
    public void clearTarget() {
        queueStoppingSeries.clear();

        ArrayList<Integer> newStoppingSerie = new ArrayList<Integer>();
        queueStoppingSeries.add(newStoppingSerie);

        stateLift = NO_MOVE;
    }

    /**
     * Donne la longueur du traget qu'il reste a faire
     * @param        startFloor Etage de début
     * @param        endFloor Etage de fin
     * @param        ratio Ratio de calcule pour les mouvements montants
     * @return longueur du chemin en nombre d'étage
     */
    public int getPathLength(int startFloor, int endFloor,int ratio) {
	if(this.urgence){
		return Integer.MAX_VALUE;
	}
        int length = 0;
        int lastStep = this.getFloor();
        int tmpLength;
        Iterator it = this.queueStoppingSeries.iterator();
        ArrayList tmp;
	boolean end = false;
	boolean full = ((this.driver.getMaxWeight()-this.driver.getWeight()) < this.driver.getMaxWeight()/10);
        /* Pour tous les ArrayList */
	while(it.hasNext() && !end){
		tmp = (ArrayList)(it.next());
		if(tmp.size()>0){
			int first = (Integer)tmp.get(0);
			int last = (Integer)tmp.get(tmp.size()-1);
			int s = this.getRequestState(startFloor, endFloor);
			if( !full &&
				s == this.getRequestState(lastStep, first) &&
				(
					(
						s == Lift.MOVE_ASC &&
						lastStep <= startFloor &&
						first >= endFloor
					) ||
					(
						s == Lift.MOVE_DESC &&
						lastStep >= startFloor &&
						first <= endFloor
					)
				)
			){
				end = true;
				tmpLength = lastStep - startFloor;
				// Si l'on monte
				if(tmpLength < 0){
					tmpLength *= -(ratio);
				}
				length += tmpLength;

			}
			else{
				tmpLength = lastStep - first;
				// Si l'on monte
				if (tmpLength < 0) {
					tmpLength *= -(ratio);
				}
				length += tmpLength;
				if( !full && 
					s == this.getRequestState(first, last) &&
					(
						(
							s == Lift.MOVE_ASC &&
							first <= startFloor &&
							last >= endFloor
						) ||
						(
							s == Lift.MOVE_DESC &&
							first >= startFloor &&
							last <= endFloor
						)
					)
				){
					end = true;
					tmpLength = first - startFloor;
					// Si l'on monte
					if(tmpLength < 0){
						tmpLength *= -(ratio);
					}
					length += tmpLength;
				}
				else{
					tmpLength = first - last;
					// Si l'on monte
					if (tmpLength < 0) {
						tmpLength *= -(ratio);
					}
					length += tmpLength;
				}
			}
			lastStep = last;
		}
	}
	if(!end){
		tmpLength = lastStep - startFloor;
		// Si l'on monte
		if (tmpLength < 0) {
			tmpLength *= -(ratio);
		}
		length += tmpLength;
	}
        return length;
    }

    /**
     * Donne le sens de navigation d'un traget
     * @param startFloor
     * @param endFloor
     * @return -1 si on descend, 1 si on monte ou 0 si on bouge pas
     */
    public int getRequestState(int startFloor, int endFloor) {
        if (startFloor<endFloor) {
            return 1;
        } else if (startFloor>endFloor) {
            return -1;
        } else {
            return 0;
        }
    }


    /**
     * Mise a jour en du pattern Observer
     * @param arg0 L'objet observé
     * @param arg1 les arguments
     */
    public void update(Observable arg0, Object arg1) {
        if (arg0 instanceof DriverLift) {
            this.stateLift = (Integer) arg1;
            DriverLift dLift = (DriverLift) arg0;
            if (this.queueStoppingSeries.size() > 0 && this.queueStoppingSeries.get(0).size() > 0) {
                if (dLift.getFloor() == this.queueStoppingSeries.get(0).get(0) && dLift.doorOpened()) {
                    this.queueStoppingSeries.get(0).remove(0);
                    if (this.queueStoppingSeries.size() > 1 && this.queueStoppingSeries.get(0).isEmpty()) {
                        this.queueStoppingSeries.remove(0);
                    }
                    if (!this.queueStoppingSeries.isEmpty() && !this.queueStoppingSeries.get(0).isEmpty()) {
                        this.stateLift = this.getRequestState(this.getFloor(), this.queueStoppingSeries.get(0).get(0));
                        dLift.moveTo(this.queueStoppingSeries.get(0).get(0));
                    } else {
                        this.stateLift = Lift.NO_MOVE;
                        this.urgence = false;
                    }
                }
            }
        }
    }
    /**
     * Modifie l'étage actuelle
     * @param val nouvelle valeur
     * @throws Exception Si l'étage n'est pas valide
     */
    public void setFloor(int val) throws Exception{
        if(val <0 || val > Utils.NB_FLOORS){
            throw new Exception("setFloor : le numéro d'étage n'est pas valide.");
	}
	this.driver.setFloor(val);
    }

    /**
     * Réinitialise l'ascenseur
     */
    public void reset(){
	    this.clearTarget();
	    this.driver.reset();
    }
}
